using System;
using System.Collections.Generic;
using System.Runtime.Serialization;

namespace Ns.Data.Entity
{
    /// <summary>
    /// Represents a type supporting change tracking, change notification and generic identifier.
    /// </summary>
    /// <typeparam name="TId">The type of the <see cref="Id"/> property.</typeparam>    
    /// <remarks>Storage members are marked as protected in order to enable mapping with LinqToSql.</remarks>
#if !SILVERLIGHT
    [Serializable]
#endif
    [DataContract(Name = "IdEntityOf{0}")]
    public abstract class IdEntity<TId>
        : PersistEntity
        , IIdEntity<TId>
        , IEquatable<IdEntity<TId>>
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="IdEntity&lt;TId&gt;"/> class 
        /// with the <see cref="PersistEntity.CurrentPersistChange"/> 
        /// and according to the <see cref="PersistEntity.CurrentChangeTracking"/>.
        /// </summary>
        public IdEntity()
            : this(CurrentPersistChange
            ?? ((CurrentChangeTracking == ChangeTracking.Enabled)
                ? PersistChange.Insert
                : PersistChange.None)
            , CurrentChangeTracking)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="IdEntity&lt;TId&gt;"/> class 
        /// with the <see cref="PersistEntity.CurrentPersistChange"/> 
        /// and according to the specified <see cref="ChangeTracking"/>.
        /// </summary>
        /// <param name="changeTracking">The change tracking.</param>
        public IdEntity(ChangeTracking changeTracking)
            : this(PersistEntity.CurrentPersistChange
            ?? ((changeTracking == ChangeTracking.Enabled)
                ? PersistChange.Insert
                : PersistChange.None)
            , changeTracking)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="IdEntity&lt;TId&gt;"/> class 
        /// with the specified <see cref="PersistChange"/> 
        /// and according to the <see cref="PersistEntity.CurrentChangeTracking"/>.
        /// </summary>
        /// <param name="persistChange">The persist change.</param>
        public IdEntity(PersistChange persistChange)
            : this(persistChange, PersistEntity.CurrentChangeTracking)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="IdEntity&lt;TId&gt;"/> class 
        /// with the specified <see cref="PersistChange"/> 
        /// and according to the specified <see cref="ChangeTracking"/>.
        /// </summary>
        /// <param name="persistChange">The persist change.</param>
        /// <param name="changeTracking">The change tracking.</param>
        public IdEntity(PersistChange persistChange, ChangeTracking changeTracking)
            : base(persistChange)
        {
            if (changeTracking == ChangeTracking.Enabled && ChangingId != null)
            {
                var args = new ChangingEventArgs<TId>();
                ChangingId(this, args);
                _id = args.Result;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="IdEntity&lt;TId&gt;"/> class
        /// copied from the specified <paramref name="entity"/>.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <remarks>This constructor ignores any <see cref="ChangeTrackingScope"/> and <see cref="PersistChangeScope"/>.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="entity"/> is null.</exception>
        protected IdEntity(IdEntity<TId> entity)
            : base(entity)
        {
            this._id = entity._id;
        }

        #endregion

        #region IIdEntity<TId> Members

        /// <ignore/>
        protected TId _id;

        /// <summary>
        /// Gets or sets the entity identifier.
        /// </summary>
        /// <value>The entity Id.</value>
        [DataMember(EmitDefaultValue = false)]
        public virtual TId Id
        {
            get { return _id; }
            set { _id = value; }
        }
        #endregion

        #region Overriden Members

        /// <summary>
        /// Converts the value of the current <see cref="IdEntity&lt;TId&gt;"/> object to its equivalent string representation. 
        /// </summary>
        /// <returns>A string representation of the value of the current <see cref="IdEntity&lt;TId&gt;"/> object.</returns>
        public override string ToString()
        {
            return string.Format("{0}, Id = {1}"
                , base.ToString()
                , Id);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            int hash = 0x4043ed47;
            hash = (hash * -1521134295) + PersistChange.GetHashCode();
            hash = (hash * -1521134295) + (object.Equals(Id, default(TId)) ? 0 : Id.GetHashCode());
            return hash;
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            return (obj is IdEntity<TId>)
                ? Equals((IdEntity<TId>)obj)
                : false;
        }

        #endregion

        #region IEquatable<IdEntity<TId>> Members

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <remarks>To compare uniquely on the <see cref="IdEntity&lt;TId&gt;.Id"/> property consider using the <see cref="Ns.Collections.Generic.MemberEqualityComparer&lt;T&gt;"/>.</remarks>
        /// <example>
        /// In the example below, comparaison of the entities is perfomed uniquely on the <see cref="IdEntity&lt;TId&gt;.Id"/> property.
        /// <code>        
        ///     var distinct = entities.Distinct(MemberEqualityComparer&lt;IdEntity&lt;TId&gt;&gt;.Default(e => e.Id));
        /// </code>
        /// </example>
        public bool Equals(IdEntity<TId> other)
        {
            return ReferenceEquals(this, other) ||
                   (other != null
                    && object.Equals(Id, other.Id)
                    && object.Equals(PersistChange, other.PersistChange));
        }

        #endregion

        #region Events
        /// <summary>
        /// Clears handlers of the <see cref="ChangingId"/> event.
        /// </summary>
        public static void ClearEventHandlers()
        {
            ChangingId = null;
        }

        /// <summary>
        /// Occurs when the <see cref="Id"/> gonna change.
        /// </summary>
        /// <remarks>This event enables centralization of generation.</remarks>
        public static event EventHandler<ChangingEventArgs<TId>> ChangingId;
        #endregion
    }
}